/**
 * 
 */
package com.googlecode.cswish.model;

import java.util.HashMap;
import java.util.Map;

import com.googlecode.cswish.util.OgnlExUtil;
import com.googlecode.cswish.view.TagManager;

/**
 * Describe the element information, it call UIBean 'type' or template 'type' to show the context
 * <p>
 * Example:<p>
 * <code>
 * pageElement.setType(TextField.class.getName());<br>
 * pageElement.setType("link.ftl");
 * </code>
 * @author Jerry.Feng Date: 2008-4-8
 *
 */
public class PageElement {

	private final static String NULL = "";
	
	protected OgnlExUtil ognlExUtil;
	
	// Use a link structure to record the whole page tree
	// the parent element
	protected PageElement parent;
	
	private String id;
	
	protected boolean visible = true;
	
	protected int index;
	
	// <? extends org.apache.struts2.components.Component>
	// ftl template
	protected String type;
	
	protected String shortName;

	protected String name;
	
	protected String label;
	
	protected String labelPosition;
	
	protected boolean required;

	protected Object value;
	protected Object cachedNameValue;
	
	protected String theme;
	
	protected boolean parseOnRuntime;
	
	protected TextModel textModel;

	protected Map extraParams;
	
	protected int size;
	
	protected int length;
	
	protected int colspan = 2;
	
	protected boolean modelField;
	
	public PageElement(OgnlExUtil ognlExUtil) {
		this.ognlExUtil = ognlExUtil;
	}
	
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public boolean isElement() {
		return true;
	}

	public PageElement getParent() {
		return parent;
	}
	
	public PageElement getTop() {
		PageElement top = this;
		
		while (top.getParent() != null) {
			top = top.getParent();
		}
		return top;
	}
	
	/**
	 * Please call refreshCache() if it's PageElements
	 * 
	 * @param parent
	 */
	public void setParent(PageElement parent) {
		this.parent = parent;
	}

	public boolean isVisible() {
		return visible;
	}

	public void setVisible(boolean visible) {
		this.visible = visible;
	}
	
	public int getIndex() {
		return index;
	}

	public PageElement setIndex(int index) {
		this.index = index;
		return this;
	}

	public String getType() {
		return type;
	}

	public PageElement setType(String type) {
		this.type = type;
		return this;
	}
	
	// FIXME: is there better way to resolve the package reference problem?
	public PageElement setTag(String tagName) {
		this.type = TagManager.getInstance().getComponentClass(tagName).getName();
		return this;
	}

	public String getShortName() {
		if (shortName == null) {
			shortName = getId();
		}
		return shortName;
	}

	public PageElement setShortName(String shortName) {
		this.shortName = shortName;
		return this;
	}

	public String getLabel() {
		return label;
	}
	
	// skip [x]
	protected String format(String name) {
		int index = name.indexOf('[');
		
		if (index > 0) {
			StringBuilder sb = new StringBuilder(name.length());
			int next = 0;
			do {
				String txt = name.substring(next, index);
				sb.append(txt);
				next = name.indexOf(']', index + 1) + 1;
				if (next > index) {
					index = name.indexOf('[', next);
				} else {
					index = -1;
					next = name.length() - 1;
				}
			} while (index != -1);
			sb.append(name.substring(next));
			name = sb.toString();
		}
		return name;
	}

	public PageElement setLabel(String title) {
		this.label = title;
		return this;
	}

	public int getSize() {
		return size;
	}

	public PageElement setSize(int size) {
		this.size = size;
		return this;
	}

	public int getLength() {
		return length;
	}

	public PageElement setLength(int length) {
		this.length = length;
		return this;
	}

	public int getColspan() {
		return colspan;
	}

	public PageElement setColspan(int colspan) {
		this.colspan = colspan;
		return this;
	}

	public boolean isRequired() {
		return required;
	}

	public PageElement setRequired(boolean required) {
		this.required = required;
		return this;
	}

	public Object getValue() {
		Object theValue;
		if (value != null) {
			theValue = value;
		} else {
			if (cachedNameValue == null) {
				cachedNameValue = NULL;
				// get the name value
				if (parent != null && getShortName() != null) {
					Object parentValue = getParent().getValue();
					if (parentValue != null && ognlExUtil != null) {
						cachedNameValue = ognlExUtil.getValue(getShortName(), new HashMap(), parentValue);
					}
				}
				
			}
			theValue = cachedNameValue == NULL ? null : cachedNameValue;
		}
		
		return theValue;
	}

	public PageElement setValue(Object value) {
		this.value = value;
		return this;
	}

	public String getTheme() {
		return theme;
	}

	public PageElement setTheme(String theme) {
		this.theme = theme;
		return this;
	}

	// the method is only used to simplify the function call of the page
	public String getName() {
		if (name == null) {
			name = getShortName();
		}
		return name;
	}

	/**
	 * Please call refreshCache() if it has parent object
	 * 
	 * @param name
	 * @return
	 */
	public PageElement setName(String name) {
		this.name = name;
		return this;
	}

	public TextModel getTextModel() {
		return textModel;
	}

	public PageElement setTextModel(String textModel) {
		this.textModel = TextModel.toTextModel(textModel);
		return this;
	}
	
	public PageElement setTextModel(TextModel textModel) {
		this.textModel = textModel;
		return this;
	}
	
	public Map getExtraParams() {
		return getExtraParams(true);
	}

	public Map getExtraParams(boolean autoCreate) {
		if (extraParams == null && autoCreate) {
			extraParams = new HashMap();
		}
		return extraParams;
	}

	public PageElement setExtraParams(Map extraParams) {
		this.extraParams = extraParams;
		return this;
	}
	
	public PageElement addExtraParams(String key, Object value) {
		getExtraParams(true).put(key, value);
		return this;
	}
	
	public boolean isParseOnRuntime() {
		return parseOnRuntime;
	}

	public void setParseOnRuntime(boolean parseOnRuntime) {
		this.parseOnRuntime = parseOnRuntime;
	}

	public boolean isModelField() {
		return modelField;
	}

	public void setModelField(boolean modelField) {
		this.modelField = modelField;
	}

	public static String getNULL() {
		return NULL;
	}

	public String getLabelPosition() {
		return labelPosition;
	}

	public void setLabelPosition(String labelPosition) {
		this.labelPosition = labelPosition;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		int index = type.lastIndexOf('.');
		index = type.lastIndexOf('.', index - 1);
		sb.append("[shortName=").append(getShortName());
		sb.append(" type=").append(type.substring(index + 1));
		sb.append(" name=").append(getName()).append(']');
		return sb.toString();
	}
	
	public PageElement clone() {
		PageElement cloned = new PageElement(this.ognlExUtil);
		cloneTo(cloned);
		return cloned;
	}
	
	public void cloneTo(PageElement cloned) {
		cloned.setId(getId());
		cloned.setVisible(isVisible());
		cloned.setType(getType());
		cloned.setExtraParams(getExtraParams());
		cloned.setShortName(getShortName());
		cloned.setModelField(isModelField());
		cloned.setName(getName());
		if (label != null) {
			cloned.setLabel(label);
		}
		cloned.setRequired(isRequired());
		cloned.setLength(getLength());
		cloned.setTheme(getTheme());
		cloned.setParseOnRuntime(isParseOnRuntime());
		cloned.setTextModel(getTextModel());
		cloned.setValue(getValue());
		cloned.setColspan(getColspan());
		cloned.setLabelPosition(getLabelPosition());
	}
	
	public int hashCode() {
		if (getId() != null) {
			return getId().hashCode();
		} else {
			return super.hashCode();
		}
	}
	
	public boolean equals(Object another) {
		if (this.getId() != null && another != null && another instanceof PageElement) {
			PageElement pageElement = (PageElement) another;
			return this.getId().equals(pageElement.getId());
		}
		return false;
	}
}